﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using ExtensionMethods;
using GsmComm.GsmCommunication;
using GsmComm.PduConverter;
using HardwareUtils;
using Lap.Collections;
using Lap.Extensions.Core;
using PpSmsSender.Db;

namespace PpSmsSender
{
    public class SmsSender : IDisposable
    {
        public static string InternalMessageDesignator = "---:";
        public readonly ConcurrentQueue<SmsMessage> FailedSendMessages = new ConcurrentQueue<SmsMessage>();
        public readonly Action<string> OutputLn;
        public readonly ConcurrentQueue<SmsMessage> RecievedMessages = new ConcurrentQueue<SmsMessage>();
        public readonly ConcurrentQueue<RecievedMessageStatus> RecievedStatuses = new ConcurrentQueue<RecievedMessageStatus>();
        public readonly ConcurrentQueue<SmsMessage> SentMessages = new ConcurrentQueue<SmsMessage>();
        private readonly ConcurrentQueue<Action> _actionsOnMain = new ConcurrentQueue<Action>();
        private readonly string _configurationfile = AppDomain.CurrentDomain.BaseDirectory + "\\Modems.xml";

        private readonly ConcurrentPriorityQueue<SmsMessage> _sendMessages = new ConcurrentPriorityQueue<SmsMessage>(15);

        public ConcurrentQueue<DeviceConnectEvent> DeviceEventQueue = new ConcurrentQueue<DeviceConnectEvent>();

        public Action<SmsMessage> FailedMessageCallback;
        public ConcurrentDictionary<string, ModemPortInfo> Modems = new ConcurrentDictionary<string, ModemPortInfo>();
        public Action<string> Output;
        public int PauseAfterSend = 200;
        public Action<SmsMessage> QueueBackCallback;
        public Action<SmsMessage> RecieveCallback;
        public Action<RecievedMessageStatus> RecieveStatusCallback;
        public Action<SmsMessage> SentMessageCallback;

        private Configuration _configuration;
        private PollingThread _eventLoop;
        private PollingThread _notifications;
        // ReSharper disable NotAccessedField.Local
        private Task _tryingReconnect;
        // ReSharper restore NotAccessedField.Local
        private UsbEventWatcher _usbEventWatcher;
        private DateTime _lastStoreCheck;


        public SmsSender(Action<SmsMessage> recieveCallback, Type phoneNumberType, Action<string> debugOutput = null)
            : this(recieveCallback, phoneNumberType, null, debugOutput)
        {
        }

        public SmsSender(Action<SmsMessage> recieveCallback, string configFile, Action<string> debugOutput = null)
            : this(recieveCallback, null, configFile, debugOutput)
        {
        }

        public SmsSender(Action<SmsMessage> recieveCallback, Type phoneNumberType, string configFile, Action<string> debugOutput = null)
        {
            this.OutputLn = s => this.Output(s + "\r\n");
            this.Output = debugOutput ?? this.DebugOutput;
            this.RecieveCallback = recieveCallback;
            if (phoneNumberType != null)
            {
                PhoneNumber.FactoryPhoneNumberType = phoneNumberType;
            }
            if (configFile != null)
            {
                this._configurationfile = EnvironmentUtils.RootPath(configFile);
            }
            this.LoadConfiguration();
        }

        protected PollingThread.State ThreadState
        {
            get { return this._eventLoop == null ? PollingThread.State.Stopped : this._eventLoop.ThreadState; }
        }

        public int SendQueueLength
        {
            get { return this._sendMessages.PrioRange.Sum(p => this._sendMessages[p].Count); }
        }

        public IConfiguration ConfigRead
        {
            get { return this._configuration; }
        }

        protected Configuration Configuration
        {
            get { return this._configuration; }
        }

        public void SendSms(string text, string reciever, string port = null, string sendingnumber = null, int prio = 8, string callbackid = null)
        {
            SmsMessage msg = new SmsMessage
                                 {
                                     Number = reciever,
                                     Text = text,
                                     Callbackid = callbackid,
                                     Priority = prio,
                                     RecievedBy = sendingnumber
                                 };
            this.MessageStatusChanged(msg, 0);
            this._sendMessages[prio].Enqueue(msg);
        }

        public void SendSms(SmsMessage msg)
        {
            msg.Type = SmsMessage.Types.Queued;
            this.MessageStatusChanged(msg, 0);
            this._sendMessages[msg.Priority].Enqueue(msg);
            this._eventLoop.Resume();
        }

        public void Start()
        {
            if (this.ThreadState == PollingThread.State.Running)
            {
                throw new InvalidAsynchronousStateException("Already running");
            }
            this.OutputLn("Starting SmsSender threads");
            this._eventLoop =
                new PollingThread(thread =>
                                      {
                                          this.EventLoop();
                                          return false;
                                      }, "SmsSender", Debugger.IsAttached ? 2000 : 100);
            this._eventLoop.Start();


            this._notifications =
                new PollingThread(
                    thread =>
                    {
                        this.CheckConnectivity();

                        return false;
                    }, "Notifications", TimeSpan.FromMinutes(15));
            this._notifications.Start();
        }

        private void EventLoop()
        {
            this.CheckDeviceEventQueue();
            this.WatchForIncomingOperatorMessages();
            this.PerformCallbackActions();

            this.HandleSendQueue();
            Thread.Sleep(0);

            if (DateTime.Now.Subtract(this._lastStoreCheck).TotalSeconds > 30)
            {
                this._lastStoreCheck = DateTime.Now;
                this.Modems.Values.ForEach(m => m.ModemHandler.EnqueueStoredMessages(PhoneMessageStatus.ReceivedUnread));
            }

            this.HandleFailedQueue();
            if (this.RecieveStatusCallback != null)
            {
                RecievedMessageStatus stat;
                while (this.RecievedStatuses.TryDequeue(out stat))
                {
                    this.RecieveStatusCallback(stat);
                }
            }
        }

        private void HandleFailedQueue()
        {
            SmsMessage msg;
            while (this.FailedSendMessages.TryDequeue(out msg))
            {
                if (msg.Status == (int)SmsMessage.Statuses.SendFailed && this.FailedMessageCallback != null && msg.FailCounter > 3)
                {
                    SmsMessage msg1 = msg;
                    this.InvokeOnMain(() => this.FailedMessageCallback(msg1));
                }
                else if (msg.Status == (int)SmsMessage.Statuses.SendFailed && msg.FailCounter <= 3)
                {
                    this._sendMessages[msg.Priority].Enqueue(msg);
                }
            }
        }

        private void PerformCallbackActions()
        {
            if (this._eventLoop.IsCurrentThread)
            {
                Action action;
                while (this._actionsOnMain.TryDequeue(out action))
                {
                    action();
                }
            }
            else
            {
                while (this._actionsOnMain.Count > 0)
                {
                    Thread.Sleep(50);
                }
            }
        }

        public void Stop()
        {
            this.Modems.Values.ForEach(m => m.ModemHandler.TryStop());
            this._eventLoop.TryStop();
            this._notifications.TryStop();
        }

        public void MessageStatusChanged(SmsMessage msg, int oldStatus)
        {
            if (msg.Status == (int)SmsMessage.Statuses.SendDone)
            {
                this.InvokeOnMain(() => this.SentMessageCallback(msg));
            }

            if (msg.Status == (int)SmsMessage.Statuses.SendException && msg.FailCounter < 3)
            {
                this.InvokeOnMain(() => this._sendMessages[msg.Priority].Enqueue(msg)); //retry failed due to exceptino in Send
            }
            if (msg.Status == (int)SmsMessage.Statuses.SendFailed && this.FailedMessageCallback != null)
            {
                this.InvokeOnMain(() => this.FailedMessageCallback(msg));
            }

            if (msg.Type == SmsMessage.Types.Recieved && this.RecieveCallback != null)
            {
                this.InvokeOnMain(() => this.RecieveCallback(msg));
            }
            this._eventLoop.Resume();
        }

        private void InvokeOnMain(Action action)
        {
            this._actionsOnMain.Enqueue(action);
        }

        private void CheckConnectivity()
        {
            this.Modems.Values
                .Where(m => m.ModemHandler.LastAttemptedSend > DateTime.Now.AddMinutes(-6)
                            && m.ModemHandler.LastAttemptedSend > m.ModemHandler.LastSuccessfulSend.AddMinutes(5))
                .ForEach(
                    m =>
                    {
                        string errorMessage = "";
                        SmsMessage sms = new SmsMessage(InternalMessageDesignator + "NotifyTest", m.ModemHandler.Config.PhoneNumber);
                        sms.Priority = 1;
                        bool failedToSend = false;
                        try
                        {
                            m.ModemHandler.SendMessage(sms);
                        }
                        catch (Exception ex)
                        {
                            failedToSend = true;
                            errorMessage += "Exception in CheckConnectivity for " + m.ComPort + ":" + ex.Message + "\r\n" + ex.StackTrace + "\r\n";
                        }

                        bool hasconn = false;
                        if (!failedToSend)
                        {
                            SmsMessage msg;
                            var tempQueue = new ConcurrentQueue<SmsMessage>();
                            while (m.ModemHandler.RecievedInternalMessages.TryDequeue(out msg))
                            {
                                tempQueue.Enqueue(msg);
                            }

                            int cnt = m.ModemHandler.RecievedInternalMessages.Count;
                            int loop = 0;
                            while (cnt == m.ModemHandler.RecievedInternalMessages.Count
                                   && ++loop < 10)
                            {
                                Thread.Sleep(TimeSpan.FromSeconds(10));
                            }

                            while (m.ModemHandler.RecievedInternalMessages.TryDequeue(out msg))
                            {
                                tempQueue.Enqueue(msg);
                            }

                            while (tempQueue.TryDequeue(out msg))
                            {
                                if (msg.Text.StartsWith(InternalMessageDesignator + "NotifyTest"))
                                {
                                    hasconn = true;
                                }
                                else
                                {
                                    m.ModemHandler.RecievedInternalMessages.Enqueue(msg);
                                }
                            }

                            if (!hasconn)
                            {
                                errorMessage += "Did not recive NotifyTest message on " + m.ComPort;
                            }
                        }
                        if (!hasconn)
                        {
                            this.OutputLn("Connectivity check failed for modem on" + m.ComPort);
                            this.OutputLn(errorMessage);

                            if (!ConfigurationManager.AppSettings["notifyByPhone"].IsNullOrWhiteSpace())
                            {
                                this.SendSms("Connectivity fail:" + errorMessage, ConfigurationManager.AppSettings["notifyByPhone"], prio: 1);
                            }

                            if (!ConfigurationManager.AppSettings["notifyByMail"].IsNullOrWhiteSpace())
                            {
                                DbConnectionStringBuilder par = new DbConnectionStringBuilder();
                                par.ConnectionString = ConfigurationManager.AppSettings["notifyByMail"];

                                int port = par.ContainsKey("Port") ? ("" + par["Port"]).Parse(25) : 25;
                                using (SmtpClient client = new SmtpClient("" + par["Smtp"], port))
                                {
                                    client.Credentials = new NetworkCredential(GetParam(par, "User Id"), GetParam(par, "Password"));
                                    if (GetParam(par, "Tls").Parse(false))
                                    {
                                        client.EnableSsl = true;
                                    }
                                    client.DeliveryMethod = SmtpDeliveryMethod.Network;
                                    using (MailMessage mail = new MailMessage())
                                    {
                                        mail.To.Add(GetParam(par, "To"));
                                        mail.From = new MailAddress(GetParam(par, "From"));
                                        mail.Body = "Connectivity fail:\r\n" + errorMessage;

                                        client.Send(mail);
                                    }
                                }
                            }
                        }
                    });

        }

        private static string GetParam(DbConnectionStringBuilder par, string userId, string defaultvalue = "")
        {
            return par.ContainsKey(userId) ? ("" + par[userId]) : defaultvalue;
        }


        private void HandleSendQueue()
        {
            int qLength = this.SendQueueLength;

            if (qLength == 0)
            {
                return;
            }
            for (int ix = 0; ix < qLength; ++ix)
            {
                ModemPortInfo targetNodem = this.Modems.Values
                    .Where(v => v.ModemHandler.IsReciveOnly == false && v.ModemHandler.SendQueueLength < 5)
                    .OrderBy(v => v.ModemHandler.SendQueueLength)
                    .ThenBy(v => v.ModemHandler.SentMessageCount)
                    .FirstOrDefault();

                if (targetNodem == null)
                {
                    return;
                }

                foreach (int p in this._sendMessages.PrioRange)
                {
                    SmsMessage msgToSend;
                    if (this._sendMessages[p].TryPeek(out msgToSend))
                    {
                        if (this._sendMessages[p].TryDequeue(out msgToSend))
                        {
                            if (!this.Configuration.IsSendAlllowed(msgToSend.Priority, DateTime.Now))
                            {
                                if (this.QueueBackCallback != null)
                                {
                                    this.InvokeOnMain(() => this.QueueBackCallback(msgToSend));
                                }
                            }
                            else
                            {
                                string sendp = this.SetPortNumber(msgToSend);
                                if (sendp == null)
                                {
                                    targetNodem.ModemHandler.EnqueueCommand(msgToSend);
                                }
                                else
                                {
                                    this.Modems[sendp].ModemHandler.EnqueueCommand(msgToSend);
                                }
                                break;
                            }
                        }
                    }
                }
                Thread.Sleep(0);
            }
        }

        private string SetPortNumber(SmsMessage cmdToSend)
        {
            string cmdToSendPort = null;
            if (cmdToSend.RecievedBy_Number != null)
            {
                cmdToSendPort = this.FindPortForNumber(cmdToSend.RecievedBy_Number);
            }
            string sendp = cmdToSendPort;
            if (cmdToSendPort != null
                && this.Modems[cmdToSendPort].ModemHandler.IsReciveOnly)
            {
                sendp = null;
            }
            return sendp;
        }

        private void WatchForIncomingOperatorMessages()
        {
            this.Modems.Values
                .ForEach(
                    m =>
                    {
                        var tempQueue = new ConcurrentQueue<SmsMessage>();
                        SmsMessage msg;

                        while (m.ModemHandler.OperatorMessages.TryDequeue(out msg))
                        {
                            bool handled = false;
                            SmsMessage msg2 = msg;
                            m.ModemHandler.Config.ControlMessages.Keys
                                .ForEach(
                                    key =>
                                    {
                                        var cms = m.ModemHandler.Config.ControlMessages[key];
                                        SmsMessage msg1 = msg2;
                                        if (cms.Any(cm => cm.IsMatch(msg1.Text)))
                                        {
                                            if (key == Configuration.ControlMessage.StopMsg)
                                            {
                                                m.ModemHandler.Config.IsRecieveOnly = true;
                                                handled = true;
                                            }
                                            if (key == Configuration.ControlMessage.StartMsg)
                                            {
                                                m.ModemHandler.Config.IsRecieveOnly = false;
                                                handled = true;
                                            }
                                        }
                                    });

                            if (!handled)
                            {
                                tempQueue.Enqueue(msg);
                            }
                            Thread.Sleep(0);
                        }
                        while (tempQueue.TryDequeue(out msg))
                        {
                            m.ModemHandler.OperatorMessages.Enqueue(msg);
                        }
                    });
        }


        private bool CatchNumberReply(IDictionary<string, ModemPortInfo> modemPortInfos, SmsMessage msg)
        {
            if (msg.Text.StartsWith(InternalMessageDesignator) && msg.Text.EndsWith(":---"))
            {
                string port = msg.Text.Split(':')[1];
                if (modemPortInfos.ContainsKey(port))
                {
                    modemPortInfos[port].Config.PhoneNumber = msg.Number;

                    this.Configuration.Modems
                        .Where(x => x.PhoneNumber == msg.Number)
                        .ForEach(confModem =>
                                     {
                                         confModem.Serial = modemPortInfos[port].Config.Serial;
                                         modemPortInfos[port].ModemHandler.Config = confModem;
                                     });
                    return true;
                }
            }
            return false;
        }

        public int InitModemList(string pattern = null)
        {
            var modemPortInfos = this.EnumerateModems();

            modemPortInfos.Values.ForEach(this.AddOneModem);

            this.OutputLn("Starting USB connection listener");
            this._usbEventWatcher = new UsbEventWatcher(this.HandleDeviceEvents);
            this.OutputLn("Started USB connection listener");

            //find out phone numbers by sending sms from unknown modems to one that is known
            string phoneNumber = this.Configuration.Modems
                .Where(m => !m.PhoneNumber.IsEmpty())
                .Select(m => m.PhoneNumber).FirstOrDefault();

            if (phoneNumber == null)
            {
                throw new Exception("At least one modem must have a known number in Modems.xml");
            }

            if (modemPortInfos.Values.Any(m => m.Config.PhoneNumber.IsEmpty()))
            {
                //there are missing numbers
                this.OutputLn("Figure out phone numbers");

                modemPortInfos.Values
                    .Where(m => m.Config.PhoneNumber.IsEmpty())
                    .ForEach(
                        m =>
                        {
                            SmsMessage msg = new SmsMessage { Text = InternalMessageDesignator + m.ComPort + ":---", Number = phoneNumber };
                            m.ModemHandler.SendMessage(msg); //Sending from specific device
                        });

                //Wait for replies
                DateTime t1 = DateTime.Now.AddSeconds(120);
                while (DateTime.Now < t1)
                {
                    if (modemPortInfos.Values.All(m => !m.Config.PhoneNumber.IsEmpty()))
                    {
                        //break if we have all numbers
                        break;
                    }
                    Thread.Sleep(500);
                    var tempQueue = new ConcurrentQueue<SmsMessage>();
                    modemPortInfos.Values
                        .ForEach(
                            m =>
                            {
                                SmsMessage msg;
                                while (m.ModemHandler.RecievedInternalMessages.TryDequeue(out msg))
                                {
                                    if (!this.CatchNumberReply(modemPortInfos, msg))
                                    {
                                        tempQueue.Enqueue(msg);
                                    }
                                }
                                //requeue other
                                tempQueue.ForEach(tm => m.ModemHandler.RecievedInternalMessages.Enqueue(tm));
                            });

                    this.OutputLn("Figure out phone numbers - done.");
                }
                this.SaveConfiguration();
                if (modemPortInfos.Values.Any(m => m.Config.PhoneNumber.IsEmpty()))
                {
                    throw new Exception("Failed to figure out phone numbers for modems");
                }
            }
            return this.Modems.Count;
        }

        private void LoadConfiguration()
        {
            this.OutputLn("LoadConfiguration");
            this._configuration = Configuration.Read(this._configurationfile);
        }

        private void SaveConfiguration()
        {
            this.OutputLn("SaveConfiguration");
            Configuration.Save(this.Configuration, this._configurationfile);
        }

        private string FindPortForNumber(string sendingNumber)
        {
            return this.Modems.Values
                .Where(m => m.Config.PhoneNumber == sendingNumber)
                .Select(m => m.ComPort).FirstOrDefault();
        }

        private void AddOneModem(ModemPortInfo m)
        {
            this.Modems[m.ComPort] = m;
            this.StartModem(m);
            IdentificationInfo info = m.ModemHandler.IdentifyPhone();
            this.Configuration.Modems
                .Where(x => x.Serial == info.SerialNumber)
                .ForEach(x =>
                             {
                                 m.Config = x;
                                 m.ModemHandler.Config = x;
                             });
            Configuration.OperatorRequest operatorRequest = m.ModemHandler.Config.OperatorRequests[Configuration.OperatorRequest.CheckMsg];
            if (operatorRequest != null)
            {
                string reply = m.ModemHandler.SendUssdRequest(operatorRequest.Ussd);
                this.OutputLn(reply);
                Match match = operatorRequest.Regex.Match(reply);
                int countRemaining = match.Groups["count"].Value.Replace(" ", "").Parse(-1);
                m.Config.SetSentSinceFilled(m.Config.PeriodLimit - countRemaining);
            }
            m.ModemHandler.Start();
        }

        private Dictionary<string, ModemPortInfo> EnumerateModems()
        {
            this.OutputLn("\r\nEnumerating modems");
            var patterns = this.Configuration.Modems
                .Select(m => new Regex(m.DevicePattern, RegexOptions.IgnoreCase | RegexOptions.Singleline))
                .ToList();
            var modemPortInfos = ModemHW.ModemPortInfos(patterns);
            string modemsList = modemPortInfos.Select(m => m.Key).ToArray().Join(", ");
            if (modemsList == "")
            {
                modemsList = "None.";
            }
            this.OutputLn("\r\nEnumerating modems - done, found " + modemsList);

            return modemPortInfos;
        }

        public void ConnectModem(ModemPortInfo connectedDev)
        {
            if (connectedDev != null)
            {
                try
                {
                    if (this.Modems.TryAdd(connectedDev.ComPort, connectedDev))
                    {
                        this.AddOneModem(connectedDev);
                        connectedDev.ModemHandler.EnqueueStoredMessages();
                        connectedDev.Config.IsPaused = false;
                    }
                    else
                    {
                        ModemPortInfo oldDev;
                        if (this.Modems.TryGetValue(connectedDev.ComPort, out oldDev))
                        {
                            if (oldDev.ModemHandler == null)
                            {
                                this.StartModem(oldDev);
                            }
                            else
                            {
                                oldDev.ModemHandler.ConnectModem(connectedDev.ComPort);
                            }
                            this.OutputLn("Modem reconnected on " + connectedDev.ComPort);
                            oldDev.ModemHandler.EnqueueStoredMessages();
                            oldDev.Config.IsPaused = false;
                        }
                    }
                }
                catch (Exception ex)
                {
                    this.OutputLn("Queue connect for " + connectedDev.ComPort + ", " + ex.Message);
                    this._tryingReconnect = Task.Factory.StartNew(
                        () =>
                        {

                            bool isConnected = false;
                            int loop = 0;
                            while (isConnected == false && loop < 6)
                            {
                                try
                                {
                                    Thread.Sleep(10000);
                                    if (connectedDev.ModemHandler._comm != null && connectedDev.ModemHandler._comm.IsConnected())
                                    {
                                        isConnected = true;
                                    }
                                    if (isConnected == false)
                                        this.ConnectModem(connectedDev);
                                }
                                catch { }
                            }
                        });
                }
            }
        }

        private void StartModem(ModemPortInfo connectedDev)
        {
            connectedDev.ModemHandler = new ModemHandler(connectedDev.ComPort, this);
            connectedDev.ModemHandler.Report += (sender, msg) => this.OutputLn(msg);
            connectedDev.ModemHandler.ConnectModem(connectedDev.ComPort);
        }

        private void DebugOutput(string s)
        {
            Debug.Write(s);
        }

        // ReSharper disable UnusedMember.Local
        private void DebugFailed(SmsMessage msg)
        // ReSharper restore UnusedMember.Local
        {
            {
                if (msg.Status > 0) //Recieved report
                {
                    MessageStatus known = new MessageStatus((byte)msg.Status);
                    if (known.IsKnownStatus())
                    {
                        //no more reports expected
                        if (known.Category == StatusCategory.PermanentError)
                        {
                            this.Output("Failed-------PermanentError:\r\n" + msg);
                        }
                        else if (known.Category == StatusCategory.TemporaryErrorNoRetry)
                        {
                            this.Output("Failed-------TemporaryErrorNoRetry:\r\n" + msg);
                        }
                    }
                    else if (msg.Status == (int)SmsMessage.Statuses.SendFailed)
                    {
                        //Failed to send
                        this.Output("Failed-------SendFailed:\r\n" + msg);
                    }
                    else if (msg.Status == (int)SmsMessage.Statuses.Sent)
                    {
                        //Timing out
                        this.Output("Failed-------TimedOut:\r\n" + msg);
                    }
                }
            }
        }

        #region Implementation of IDisposable
        public void Dispose()
        {
            this.SaveConfiguration();
            this.Stop();
            if (this._usbEventWatcher != null)
            {
                this._usbEventWatcher.Dispose();
            }
            this.Modems.Values.ForEach(m => m.ModemHandler.Dispose());
        }
        #endregion

        #region Device Events
        private void HandleDeviceEvents(string path, bool connected)
        {
            ModemPortInfo dev = this.Modems.Values.FirstOrDefault(d => d.DevInfo.Path == path);

            if (dev != null)
            {
                this.OutputLn((connected ? "Connect" : "Disconnect") + " " + dev.ComPort);
                dev.Config.IsPaused = true;
                this.DeviceEventQueue.Enqueue(new DeviceConnectEvent { Dev = dev, Connect = connected });
            }
        }

        private void CheckDeviceEventQueue()
        {
            if (this.DeviceEventQueue.Count == 0)
            {
                return;
            }
            Thread.Sleep(5000);
            var newModemlist = this.EnumerateModems();

            var deviceFirst = new Dictionary<string, bool>(); // first Event per port
            var deviceLast = new Dictionary<string, bool>(); // last Event per port


            DeviceConnectEvent deviceConnect;
            while (this.DeviceEventQueue.TryDequeue(out deviceConnect))
            {
                if (!deviceFirst.ContainsKey(deviceConnect.Dev.DevInfo.Path))
                {
                    deviceFirst[deviceConnect.Dev.DevInfo.Path] = deviceConnect.Connect;
                    deviceLast[deviceConnect.Dev.DevInfo.Path] = deviceConnect.Connect;
                }
                else
                {
                    deviceLast[deviceConnect.Dev.DevInfo.Path] = deviceConnect.Connect;
                }
            }

            if (deviceFirst.Count > 0)
            {
                deviceFirst.Keys
                    .ForEach(devId =>
                                 {
                                     if (deviceFirst[devId] == false && deviceLast[devId] == false)
                                     {
                                         //now is disconnected
                                         ModemPortInfo dev = this.Modems.Values.FirstOrDefault(m => m.DevInfo.Path == devId);
                                         this.DisconnectModem(dev);
                                     }
                                     else if (deviceFirst[devId] && deviceLast[devId])
                                     {
                                         //now is connected
                                         ModemPortInfo dev = newModemlist.Values.FirstOrDefault(m => m.DevInfo.Path == devId);
                                         this.ConnectModem(dev);
                                     }
                                     else if (deviceFirst[devId] == false && deviceLast[devId])
                                     {
                                         //disconnected, now is connected again
                                         ModemPortInfo dev1 = this.Modems.Values.FirstOrDefault(m => m.DevInfo.Path == devId);
                                         ModemPortInfo dev2 = newModemlist.Values.FirstOrDefault(m => m.DevInfo.Path == devId);
                                         this.ReConnectModem(dev1, dev2);
                                     }
                                     // else: connected, now is disconnected again, ignore
                                 });
            }
        }

        private void ReConnectModem(ModemPortInfo disconnectedDev, ModemPortInfo connectedDev)
        {
            this.DisconnectModem(disconnectedDev);
            this.ConnectModem(connectedDev);
        }

        private void DisconnectModem(ModemPortInfo disconnectedDev)
        {
            if (disconnectedDev != null)
            {
                Configuration.Modem configForDev = this.Configuration.Modems.FirstOrDefault(m => m.ModemSeq == disconnectedDev.ModemHandler.Config.ModemSeq);

                configForDev.IsPaused = true;
                disconnectedDev.ModemHandler.TryStop(); //Stop modem thread, will wait for Join

                this.PerformCallbackActions();

                // requeue send commands
                disconnectedDev.ModemHandler.TakeUnhandledCommands()
                    .ForEach(smsCommand => this._sendMessages[smsCommand.Priority].Enqueue(smsCommand));
            }
        }
        #endregion

        #region Nested type: DeviceConnectEvent
        public class DeviceConnectEvent
        {
            public bool Connect;
            public ModemPortInfo Dev;
        }
        #endregion

        #region Nested type: ModemPortInfo
        public class ModemPortInfo
        {
            public string ComPort;
            public Configuration.Modem Config;
            public PnPDevices.PnPDeviceInfo DevInfo;
            public ModemHandler ModemHandler;
        }
        #endregion
    }
}